/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Copyright 2002-2003 by Mark Wolgemuth, All rights reserved.             */
/* Copyright 2002-2003 by Employease, Inc.  All rights reserved.           */
/*                                                                         */
/* Redistribution and use in source and binary forms, with or without      */
/* modification, are permitted provided that the following conditions      */
/* are met:                                                                */
/*                                                                         */
/*    1. Redistributions of source code must retain the above copyright    */
/* notice, this list of conditions and the following disclaimer.           */
/*    2. Redistributions in binary form must reproduce the above copyright */
/* notice, this list of conditions and the following disclaimer in the     */
/* documentation and/or other materials provided with the distribution.    */
/*                                                                         */
/* THIS SOFTWARE IS PROVIDED BY THE MOD_ATHENA PROJECT ``AS IS'' AND ANY   */
/* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE       */
/* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR      */
/* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR        */
/* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,   */
/* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,     */
/* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR      */
/* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY     */
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT            */
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   */
/* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.    */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "athd.h"

static apr_pool_t *_sig_handlers_pool = NULL;
static ath_list *_sig_handlers_shutdown = NULL;
static ath_list *_sig_handlers_stop = NULL;
static ath_list *_sig_handlers_start = NULL;
static pthread_mutex_t *_sig_mutex = NULL;
static int _sig_exit = 0;

static void _athd_sig_handler_setup(apr_pool_t *pool);
static int _athd_sig_shutdown(int sig);
static int _athd_sig_stop(int sig);
static int _athd_sig_start(int sig);
static int _athd_sig_lock();
static int _athd_sig_unlock();
static void * _athd_watchdog_thread_cleanup(void *ctx);

/* end private */

typedef struct athd_sig_handler athd_sig_handler;
struct athd_sig_handler {
    athd_sig_func func;
    char *name;
    int run; /* 0=shutdown 1=startup */
};

pthread_t *
athd_watchdog_thread_setup(apr_pool_t *pool)
{
    pthread_t *watchdog_thread = NULL;
    sigset_t sigmask;
    struct sigaction sigact;
    watchdog_thread = apr_pcalloc(pool, sizeof(pthread_t));
    sigfillset(&sigmask);
    sigdelset(&sigmask, SIGKILL);
    sigdelset(&sigmask, SIGSTOP);
    sigdelset(&sigmask, SIGCONT);

#ifdef SIGFPE
    sigdelset(&sigmask, SIGFPE);
#endif
#ifdef SIGPIPE
    sigdelset(&sigmask, SIGPIPE);
    apr_signal(SIGPIPE, SIG_IGN);
#endif
    /* remove pthread unsafe sync sigs */
    /*
    sigdelset(sigmask, SIGABRT);
    sigdelset(sigmask, SIGBUS);
#ifdef SIGEMT
    sigdelset(sigmask, SIGEMT);
#endif
#ifdef SIGILL
    sigdelset(sigmask, SIGILL);
#endif
#ifdef SIGIOT
    sigdelset(sigmask, SIGIOT);
#endif
#ifdef SIGSEGV
    sigdelset(sigmask, SIGSEGV);
#endif
#ifdef SIGSYS
    sigdelset(sigmask, SIGSYS);
#endif
#ifdef SIGTRAP
    sigdelset(sigmask, SIGTRAP);
#endif
    */

    /* now pass off sig handling to it */
    pthread_sigmask(SIG_BLOCK, &sigmask, NULL);
    pthread_create(watchdog_thread, NULL, athd_watchdog_thread, NULL);
    ASSERT(watchdog_thread);
    _athd_sig_handler_setup(pool);
    return watchdog_thread;
}

void *
athd_watchdog_thread(void *ctx)
{
    int laststate, lasttype;
    sigset_t sigs;
    int signalin;
    int rv;
    int dienow = 0;

    sigemptyset(&sigs);
    sigaddset(&sigs, SIGHUP);
    sigaddset(&sigs, SIGINT);
    sigaddset(&sigs, SIGSEGV);
    sigaddset(&sigs, SIGABRT);
    sigaddset(&sigs, SIGTERM);
    sigaddset(&sigs, SIGUSR1);
    sigaddset(&sigs, SIGUSR2);
    sigaddset(&sigs, SIGALRM);
    pthread_sigmask(SIG_BLOCK, &sigs, NULL);
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &laststate);
    //pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &lasttype);
    pthread_cleanup_push((void *)_athd_watchdog_thread_cleanup, ctx);

    athd_log(ATHD_DEBUG, "Watchdog Thread: Enter");

    for (;!dienow;)
      {
	  sigset_t sigpend;
	  int waspend = 0;
	  if (rv = sigwait(&sigs, &signalin))
	    {
		athd_log_err(ATHD_CRIT, rv, "Watchdog: fatal sigwait error: ");
		ASSERT(0);
	    }
	  sigpending(&sigpend);
	  /* this is required for darwin */
	  if (sigismember(&sigpend, SIGTERM))
	    {
		waspend = 1;
		signalin = SIGTERM;
	    }
	  else if (sigismember(&sigpend, SIGINT))
	    {
		waspend = 1;
		signalin = SIGINT;
	    }
	  if (waspend)
	      athd_log(ATHD_DEBUG, "Watchdog: nested sig: %d", signalin);
	  athd_log(ATHD_DEBUG, "Watchdog: caught sig: %d", signalin);
	  if (signalin == SIGUSR1)
	    {
		athd_log(ATHD_NOTE, "Watchdog: -- LOGMARK --");
		continue;
	    }
	  else if (signalin == SIGUSR2)
	    {
		athd_log(ATHD_NOTE, "Watchdog: daemon is running!");
		continue;
	    }
	  else if (signalin == SIGSEGV)
	      athd_log(ATHD_CRIT, "Watchdog: seg fault, check for core.");
	  else if (signalin == SIGABRT)
	      athd_log(ATHD_CRIT, "Watchdog: aborted, check for core.");

	  /* all others stop */
	  _athd_sig_stop(signalin);

	  /* hup restarts */
	  if ((signalin == SIGHUP) || (signalin == SIGINT))
	    {
		athd_log(ATHD_CORE, "Watchdog: resetting internals.");
		_athd_sig_start(signalin);
	    }
	  else /* the rest cause shutdown */
	    {
		_athd_sig_shutdown(signalin);
		dienow = 1;
	    }
      }
    pthread_cleanup_pop(1);
    _sig_exit = signalin;
    pthread_exit((void *)&_sig_exit);
}

void
athd_sig_handler_register(athd_sig_func func, const char *name, int run)
{
    athd_sig_handler *handler;
    ASSERT(func);
    ASSERT(name);
    _athd_sig_lock();
    handler = apr_pcalloc(_sig_handlers_pool, sizeof(*handler));
    handler->func = func;
    handler->name = apr_pstrdup(_sig_handlers_pool, name);
    handler->run = run;
    if (run == ATHD_SHUTDOWN)
	ath_list_ap_add(_sig_handlers_pool, _sig_handlers_shutdown, handler);
    else if (run == ATHD_STOP)
	ath_list_ap_add(_sig_handlers_pool, _sig_handlers_stop, handler);
    else if (run == ATHD_START)
	ath_list_ap_add(_sig_handlers_pool, _sig_handlers_start, handler);
    else
	athd_log(ATHD_CRIT, "Illegal run level: %d", run);
    _athd_sig_unlock();
}

static void
_athd_sig_handler_setup(apr_pool_t *pool)
{
    ASSERT(pool);
    ASSERT(!_sig_handlers_pool);
    apr_pool_create(&_sig_handlers_pool, pool);
    _sig_mutex = apr_pcalloc(_sig_handlers_pool, sizeof(pthread_mutex_t));
    apr_pool_cleanup_register(_sig_handlers_pool, _sig_mutex,
			      pthread_mutex_cleanup, apr_pool_cleanup_null);
    pthread_mutex_init(_sig_mutex, NULL);
    _athd_sig_lock();
    _sig_handlers_shutdown = ath_list_ap_alloc(_sig_handlers_pool);
    _sig_handlers_stop = ath_list_ap_alloc(_sig_handlers_pool);
    _sig_handlers_start = ath_list_ap_alloc(_sig_handlers_pool);
    _athd_sig_unlock();
}

static int
_athd_sig_shutdown(int sig)
{
    ath_node *listctx;
    _athd_sig_lock();
    ath_list_rewind_r(_sig_handlers_shutdown, &listctx);
    while (ath_list_hasnext_r(_sig_handlers_shutdown, &listctx))
      {
	  int rv;
	  athd_sig_handler *handler = 
	    ath_list_getnext_r(_sig_handlers_shutdown, &listctx);
	  DEBUGOUT("shutdown: %s", handler->name);
	  rv = handler->func(sig);
	  if (rv)
	      athd_log_err(ATHD_CRIT, rv, "Watchdog: Shutdown: %s Error: ",
			   handler->name);
	  else
	      athd_log(ATHD_INFO, "Watchdog: Shutdown: %s Done.", 
		       handler->name);
      }
    _athd_sig_unlock();
    return sig;
}

static int
_athd_sig_stop(int sig)
{
    ath_node *listctx;
    _athd_sig_lock();
    ath_list_rewind_r(_sig_handlers_stop, &listctx);
    while (ath_list_hasnext_r(_sig_handlers_stop, &listctx))
      {
	  int rv;
	  athd_sig_handler *handler = 
	    ath_list_getnext_r(_sig_handlers_stop, &listctx);
	  rv = handler->func(sig);
	  if (rv)
	      athd_log_err(ATHD_CRIT, rv, "Watchdog: Stop: %s Error: ", 
			   handler->name);
	  else
	      athd_log(ATHD_INFO, "Watchdog: Stop: %s Done.", 
		       handler->name);
      }
    _athd_sig_unlock();
    return sig;
}

static int
_athd_sig_start(int sig)
{
    ath_node *listctx;
    _athd_sig_lock();
    ath_list_rewind_r(_sig_handlers_start, &listctx);
    while (ath_list_hasnext_r(_sig_handlers_start, &listctx))
      {
	  int rv;
	  athd_sig_handler *handler = 
	    ath_list_getnext_r(_sig_handlers_start, &listctx);
	  rv = handler->func(sig);
	  if (rv)
	      athd_log_err(ATHD_CORE, rv, "Watchdog: Start: %s Error: ", 
			   handler->name);
	  else
	      athd_log(ATHD_INFO, "Watchdog: Start: %s Done.", 
		       handler->name);
      }
    _athd_sig_unlock();
    return sig;
}

static int
_athd_sig_lock()
{
    return pthread_mutex_lock(_sig_mutex);
}

static int
_athd_sig_unlock()
{
    return pthread_mutex_unlock(_sig_mutex);
}

static void * _athd_watchdog_thread_cleanup(void *ctx)
{
    athd_log(ATHD_INFO, "Watchdog Thread: Exit");
}
